#include <Arduino.h>
#include <Wire.h>
#include "BLEDevice.h"
#include <OneButton.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>
#include <Adafruit_LIS3DH.h>

//http://update.local 172.20.10.12

const char* host = "update";
const char* ssid = "esp32";
const char* password = "12345678";

const gpio_num_t GS_INT_PIN   = GPIO_NUM_3;
const gpio_num_t BTN_INT_PIN  = GPIO_NUM_1;
const gpio_num_t LED_PIN      = GPIO_NUM_10;
const gpio_num_t BAT_ADC_PIN  = GPIO_NUM_0;

// 深度睡眠唤醒定时, 暂时不使用
#define CONFIG_LIGHTSLEEP_TEMER 0

// 使用LIS3DH Gsensor
#define CONFIG_BELIS3DH_DEVICE  1

// 等待扫描全部完成才处理, 用于识别存在多个相同uuid,但信号强度不同的情况
#define CONFIG_CONTINUE_SCAN  0

// LED指示灯关闭
#define CONFIG_LIGHT_SILENT 0

// 进入深度休眠时间
#if (CONFIG_CONTINUE_SCAN == 0)
  #define CONFIG_SLEEP_TIME 8000
#else
  #define CONFIG_SLEEP_TIME 30000
#endif


// 长按进入模式 时间配置
#define CONFIG_FREIGHT_LONG_TIME   6000
#define CONFIG_ENTER_OTA_LONG_TIME 4000
#define CONFIG_REBOOT_LONG_TIME    2000

// The remote service we wish to connect to.
static BLEUUID serviceUUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
// The characteristic of the remote service we are interested in.
static BLEUUID    charUUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");

#define CLICKTHRESHOLD 20

Adafruit_LIS3DH lis = Adafruit_LIS3DH();

static boolean doConnect = false;
static boolean connected = false;
static boolean ScanFail = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLEAdvertisedDevice* myDevice;
static BLEClient*  pClient;
static BLEScan* pBLEScan;

#define wdtTimeout 15000

OneButton button(BTN_INT_PIN, true);

hw_timer_t *blink_timer = NULL;
hw_timer_t *watchdog_timer = NULL;

TimerHandle_t timeoutTimer;

WebServer server(80);

volatile bool timerFlag = false; // 定时器中断标志

volatile unsigned long system_start_time = 0;

volatile uint32_t led_blink_number = 0;

volatile bool toOTAflag = false;

#define LED_ON()  digitalWrite(LED_PIN, HIGH)
#define LED_OFF() digitalWrite(LED_PIN, LOW)
#define clean_enter_deep_time() system_start_time = millis()

void led_blink(uint32_t times, bool block, uint32_t duration = 100, uint32_t interval = 100);
void ble_send_data(BLERemoteCharacteristic *pChar, boolean connectedState, String data);
void enterDeepSleep(uint64_t wakeup_time_us);

const char* loginIndex =
 "<form name='loginForm'>"
    "<table width='20%' bgcolor='A09F9F' align='center'>"
        "<tr>"
            "<td colspan=2>"
                "<center><font size=4><b>ESP32 Login Page</b></font></center>"
                "<br>"
            "</td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
             "<td>Username:</td>"
             "<td><input type='text' size=25 name='userid'><br></td>"
        "</tr>"
        "<br>"
        "<br>"
        "<tr>"
            "<td>Password:</td>"
            "<td><input type='Password' size=25 name='pwd'><br></td>"
            "<br>"
            "<br>"
        "</tr>"
        "<tr>"
            "<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
        "</tr>"
    "</table>"
"</form>"
"<script>"
    "function check(form)"
    "{"
    "if(form.userid.value=='admin' && form.pwd.value=='admin')"
    "{"
    "window.open('/serverIndex')"
    "}"
    "else"
    "{"
    " alert('Error Password or Username')/*displays error message*/"
    "}"
    "}"
"</script>";

/*
 * Server Index Page
 */

const char* serverIndex =
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
   "<input type='file' name='update'>"
        "<input type='submit' value='Update'>"
    "</form>"
 "<div id='prg'>progress: 0%</div>"
 "<script>"
  "$('form').submit(function(e){"
  "e.preventDefault();"
  "var form = $('#upload_form')[0];"
  "var data = new FormData(form);"
  " $.ajax({"
  "url: '/update',"
  "type: 'POST',"
  "data: data,"
  "contentType: false,"
  "processData:false,"
  "xhr: function() {"
  "var xhr = new window.XMLHttpRequest();"
  "xhr.upload.addEventListener('progress', function(evt) {"
  "if (evt.lengthComputable) {"
  "var per = evt.loaded / evt.total;"
  "$('#prg').html('progress: ' + Math.round(per*100) + '%');"
  "}"
  "}, false);"
  "return xhr;"
  "},"
  "success:function(d, s) {"
  "console.log('success!')"
 "},"
 "error: function (a, b, c) {"
 "}"
 "});"
 "});"
 "</script>";

void ota_update(void)
{
	byte wifi_retry_count = 0;
  Serial.println("For OTA Connect to WIFI");
	WiFi.begin(ssid, password);
	while (WiFi.status() != WL_CONNECTED) {
		delay(500);
		if (wifi_retry_count++ >= 16)break;
	}

  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

	if (WiFi.status() == WL_CONNECTED) {
    led_blink(2, true, 200, 200);
		if (!MDNS.begin(host)) { //http://update.local 172.20.10.12
			while (1);
		}
		server.on("/", HTTP_GET, []() {
		server.sendHeader("Connection", "close");
		server.send(200, "text/html", loginIndex);
		});
		server.on("/serverIndex", HTTP_GET, []() {
			server.sendHeader("Connection", "close");
			server.send(200, "text/html", serverIndex);
		});
		/*handling uploading firmware file */
		server.on("/update", HTTP_POST, []() {
			server.sendHeader("Connection", "close");
			server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
      led_blink(3, true, 200, 200);
			ESP.restart();
		}, []() {
			HTTPUpload& upload = server.upload();
			if (upload.status == UPLOAD_FILE_START) {
			Serial.printf("Update: %s\n", upload.filename.c_str());
			if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
				Update.printError(Serial);
			}
			} else if (upload.status == UPLOAD_FILE_WRITE) {
			/* flashing firmware to ESP*/
			if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
				Update.printError(Serial);
			}
			} else if (upload.status == UPLOAD_FILE_END) {
			if (Update.end(true)) { //true to set the size to the current progress
				Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
			} else {
				Update.printError(Serial);
			}
			}
		});
		server.begin();
		MDNS.addService("http", "tcp", 80);
		while (1) {
			server.handleClient();
			delay(10);
		}
	}
	WiFi.disconnect(true, true);
  WiFi.mode(WIFI_OFF); 
	MDNS.end();
	server.stop();
}

class MyClientCallback : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {
    connected = true;
    // Serial.println("onConnect");
    led_blink(2, false);
  }

  void onDisconnect(BLEClient* pclient) {
    connected = false;
    pRemoteCharacteristic = nullptr;
    // Serial.println("onDisconnect");
  }
};

bool connectToServer() {
    uint8_t retry_count = 0;

    Serial.print("Forming a connection to ");
    if (myDevice == nullptr)return false;

    Serial.println(myDevice->getAddress().toString().c_str());
    
    pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());
retry:
    // Connect to the remove BLE Server.
    if (pClient->connect(myDevice) != true) {
      delay(100);
      if (retry_count >= 1) {
        return false;
      } else {
        retry_count++;
        goto retry;
      }
    }
    retry_count = 0;
    Serial.println(" - Connected to server");

    // pClient->setMTU(517); //set client to request maximum MTU from server (default is 23 otherwise)
    // Obtain a reference to the service we are after in the remote BLE server.
    // !! in BLEClient.cpp Line:465 m_haveServices = m_semaphoreSearchCmplEvt.timedWait("getServices", 3000); change lib

    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(serviceUUID.toString().c_str());
      pClient->disconnect();
      delay(100);
      if (retry_count >= 1) {
        return false;
      } else {
        retry_count++;
        goto retry;
      }
    }

    Serial.println(" - Found our service");

    // Obtain a reference to the characteristic in the service of the remote BLE server.
    pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
    if (pRemoteCharacteristic == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our characteristic");

#if (CONFIG_CONTINUE_SCAN == 1)
    ble_send_data(pRemoteCharacteristic, connected, "r");
#else
    ble_send_data(pRemoteCharacteristic, connected, "s");
#endif    
    return true;
}

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
 /**
   * Called for each advertising BLE server.
   */
  void onResult(BLEAdvertisedDevice advertisedDevice) {

    static int best_rssi = -999;

    // Serial.print("BLE Advertised Device found: ");
    // Serial.println(advertisedDevice.toString().c_str());
    led_blink(1, false);

    // We have found a device, let us now see if it contains the service we are looking for.
    if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {
#if (CONFIG_CONTINUE_SCAN == 1)
      if (advertisedDevice.getRSSI() > best_rssi) {
        if (myDevice != nullptr) {
          delete myDevice;
          myDevice = nullptr;
        }
        best_rssi = advertisedDevice.getRSSI();
        myDevice = new BLEAdvertisedDevice(advertisedDevice);
      }
#else
      if (myDevice != nullptr) {
        delete myDevice;
        myDevice = nullptr;
      }
      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
#endif
    }
  }
};

void enterDeepSleep(uint64_t wakeup_time_us) {
  esp_err_t ret;
  Serial.println("Entering Deep Sleep...");
  BLEDevice::getScan()->stop();
  if (pClient) {
    pClient->disconnect();
  }
  if (blink_timer != nullptr) {
    timerEnd(blink_timer);
  }
  if (watchdog_timer != nullptr) {
    timerEnd(watchdog_timer);
  }
  LED_OFF(); //确保LED关闭
#if (CONFIG_BELIS3DH_DEVICE == 1)
  ret = esp_deep_sleep_enable_gpio_wakeup(1ULL << GS_INT_PIN, ESP_GPIO_WAKEUP_GPIO_HIGH);
  if (ret != 0) {
    Serial.print("DEEP GPIO ERR ");
    Serial.println(ret);
  }
#endif
  ret = esp_deep_sleep_enable_gpio_wakeup(1ULL << BTN_INT_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
  if (ret != 0) {
    Serial.print("DEEP GPIO ERR ");
    Serial.println(ret);
  }
  delay(20);
  esp_deep_sleep_start();
}

void onScanComplete(BLEScanResults scanResults)
{
  Serial.println("Scan complete!");

  if (myDevice == nullptr) {
    Serial.println("Scan result NULL!");
    ScanFail = true;
    led_blink(5, true, 500, 500);
    enterDeepSleep(CONFIG_LIGHTSLEEP_TEMER * 1000000LL);
  } else {
#if (CONFIG_CONTINUE_SCAN == 1)
  BLEDevice::getScan()->stop();
  doConnect = true;
#endif
  }
}

void led_blink(uint32_t times, bool block, uint32_t duration, uint32_t interval) {
#if (CONFIG_LIGHT_SILENT == 0)
if (block == true) {
  for (int i = 0; i < times; i++) {
    LED_ON();
    delay(duration);
    LED_OFF();
    delay(interval);
  }
} else {
  if (blink_timer == nullptr) return;
  led_blink_number = times;
  timerAlarmEnable(blink_timer);
  timerStart(blink_timer);
}
#endif
}

#define R1 75000.0  // 电阻R1的阻值，单位为欧姆
#define R2 24000.0  // 电阻R2的阻值，单位为欧姆
#define NUM_SAMPLES 100
uint32_t getStableAnalogValue(int pin) {
  long sum = 0;
  for (int i = 0; i < NUM_SAMPLES; i++) {
    sum += analogReadMilliVolts(pin);  // 读取多次ADC值
    delay(1);  // 可选，给ADC一个短暂的时间进行稳定
  }
  return sum / NUM_SAMPLES;  // 返回平均值 
}

float readVBAT() {
  uint32_t voltage = getStableAnalogValue(BAT_ADC_PIN);
  float vinVolts = (voltage * (R1 + R2) / R2) / 1000;
  return vinVolts;
}

void check_vbat_voltage() {
  float voltage = readVBAT();
  int ledTimes = 0;
  if (voltage < 2.7) {
    ledTimes = 1;
  } else if (voltage < 3.2) {
    ledTimes = 2;
  } else if (voltage < 3.7) {
    ledTimes = 3;
  } else if (voltage <= 5.0) {
    ledTimes = 4;
  }
  led_blink(ledTimes, true, 200, 200);
}

void ble_send_data(BLERemoteCharacteristic *pChar, boolean connectedState, String data)
{
  if (connectedState && (pChar != nullptr)) {
    pChar->writeValue(data.c_str(), data.length());
  }
}

void handleSingleClick() {
  Serial.println("检测到单击！");
  led_blink(1 ,false);
  ble_send_data(pRemoteCharacteristic, connected, "r");
  clean_enter_deep_time();
}

void handleDoubleClick() {
  Serial.println("检测到双击！");
  led_blink(2, false);
  ble_send_data(pRemoteCharacteristic, connected, "s");
  clean_enter_deep_time();
}


static unsigned long pressLongStartTime = 0; // 按键按下的开始时间
static unsigned long pressLongDuration = 0;  // 按键按下的持续时间
static bool ota_first_enter = true;
static bool rebot_first_enter = true;
static bool freight_first_enter = true;
void handleLongPress() {
  Serial.println("检测到长按！");
  pressLongStartTime = millis();
  ble_send_data(pRemoteCharacteristic, connected, "f");
  clean_enter_deep_time();
  BLEDevice::getScan()->stop();
}

void handlePressStop() {
  Serial.print("检测到长按 停止！- ");
  clean_enter_deep_time();
  pressLongDuration = millis() - pressLongStartTime;
  Serial.println(pressLongDuration);

  if (pressLongDuration >= CONFIG_FREIGHT_LONG_TIME) {
    delay(20);
    // 运输模式 首次按键激活
    esp_deep_sleep_enable_gpio_wakeup(1ULL << BTN_INT_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
    esp_deep_sleep_start();
  } else if (pressLongDuration >= CONFIG_ENTER_OTA_LONG_TIME) {
    Serial.println("Enter OTA");
    led_blink(4, true, 200, 200);
    toOTAflag = true;
  } else if (pressLongDuration >= CONFIG_REBOOT_LONG_TIME) {
    Serial.println("Reboot");
    led_blink(6, true, 200, 200);
    ESP.restart();
  }
  ota_first_enter = true;
  rebot_first_enter = true;
  freight_first_enter = true;
}

void handlePressDuring() {
  pressLongDuration = millis() - pressLongStartTime;
  if (pressLongDuration >= CONFIG_REBOOT_LONG_TIME && rebot_first_enter) {
    led_blink(1, false);
    rebot_first_enter = false;
  } else if (pressLongDuration >= CONFIG_ENTER_OTA_LONG_TIME && ota_first_enter) {
    led_blink(2, false);
    ota_first_enter = false;
  } else if (pressLongDuration >= CONFIG_FREIGHT_LONG_TIME && freight_first_enter) {
    led_blink(3, false);
    freight_first_enter = false;
  }
}

void ARDUINO_ISR_ATTR onBlinkTimer() {
  static volatile uint32_t toggleCount = 0;
  digitalWrite(LED_PIN, !digitalRead(LED_PIN));
  toggleCount++;
  if (toggleCount >= (led_blink_number * 2)) {
    toggleCount = 0;
    LED_OFF();
    if (blink_timer != nullptr) {
      timerStop(blink_timer);
    }
  }
}

void ARDUINO_ISR_ATTR resetModule() {
  ets_printf("reboot\n");
  esp_restart();
}

void lis3dh_init()
{
  Wire.begin(5, 4, 100000);
  if (!lis.begin(0x19)) {
    Serial.println("LIS3DH Couldnt start");
    led_blink(3, true, 1000, 1000);
    ESP.restart();
  }
  // !! 使用single唤醒,需要在库代码中屏蔽enableDRDY, 否则INT1将保持高电平
  Serial.println("LIS3DH Init");
  lis.setRange(LIS3DH_RANGE_2_G);
  lis.setClick(1, CLICKTHRESHOLD);
  lis.setPerformanceMode(LIS3DH_MODE_LOW_POWER);
}

void setup() {
  delay(20);
  Serial.begin(1500000);
  Serial.println("Hello BLE Client");

  esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
  if (wakeup_reason == ESP_SLEEP_WAKEUP_GPIO) {
    Serial.println("Woke up from deep sleep due to GPIO");
  } else {
    Serial.println("Regular start or reset need push button to start");
    // delay(200);
    // 运输模式 首次按键激活
    // esp_deep_sleep_enable_gpio_wakeup(1ULL << BTN_INT_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
    // esp_deep_sleep_start();
  }

  esp_reset_reason_t reset_reason = esp_reset_reason();
  Serial.print("reset_reason: ");
  Serial.println(reset_reason);

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  pinMode(BTN_INT_PIN, INPUT);

  button.attachClick(handleSingleClick);
  button.attachDoubleClick(handleDoubleClick);
  button.attachLongPressStart(handleLongPress);
  button.attachLongPressStop(handlePressStop);
  button.attachDuringLongPress(handlePressDuring);

  watchdog_timer = timerBegin(0, 80, true);
  timerAttachInterrupt(watchdog_timer, &resetModule, true);
  timerAlarmWrite(watchdog_timer, (wdtTimeout * 1000), false); 
  timerAlarmEnable(watchdog_timer);

  blink_timer = timerBegin(1, 80, true);
  timerAttachInterrupt(blink_timer, &onBlinkTimer, true);
  timerAlarmWrite(blink_timer, 30000, true);

  check_vbat_voltage();

#if (CONFIG_BELIS3DH_DEVICE == 1)
  lis3dh_init();
#endif

  system_start_time = millis();

  BLEDevice::init("");

  pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(500);
  pBLEScan->setWindow(500);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(10, onScanComplete, false);

  // setCpuFrequencyMhz(80);
  // //打印修改后的CPU频率
  // Serial.print("New CPU Frequency: ");
  // Serial.print(getCpuFrequencyMhz());
  // Serial.println(" MHz");
}

void loop() {
  unsigned long current_time = millis();
  if (doConnect == true) {
    if (connectToServer()) {
      Serial.println("We are now connected to the BLE Server.");
    } else {
      Serial.println("We have failed to connect to the server; there is nothin more we will do.");
      enterDeepSleep(CONFIG_LIGHTSLEEP_TEMER * 1000000LL);
    }
    doConnect = false;
  }

  if (connected) {
  }

  if (ScanFail == true) {
  }

  if (toOTAflag == true) {
    if (watchdog_timer != nullptr) {
      timerStop(watchdog_timer);
      ota_update();
      timerAlarmEnable(watchdog_timer);
      timerStart(watchdog_timer);
    } else {
      ota_update();
    }
  }

  if (timerFlag == true) {
    enterDeepSleep(CONFIG_LIGHTSLEEP_TEMER * 1000000LL);
  } else if ((current_time - system_start_time) >= CONFIG_SLEEP_TIME){
    timerFlag = true;
  }
  if (watchdog_timer != nullptr) {
    timerWrite(watchdog_timer, 0);
  }
  button.tick();
}
